#include "Col.ph"
#ifdef __GNUG__
#pragma implementation
#endif

#include "Env.h"

#include "Class.h"
#include "String.h"
#include "System.h"
#include "OrdColl.h"
#include "StreamBuf.h"
#include "ET_stdio.h"
#include "ET_stdlib.h"

//---- EnvParser ---------------------------------------------------------------

class EnvParser {
	FILE *ifp;
public:
	EnvParser(FILE *f)
		{ ifp= f; }
	virtual void KeyValue(char*, char*, char*)
		{ }
	virtual void Char(int)
		{ }
	void Parse();
};

void EnvParser::Parse()
{
	char name[200], type[200], value[200];
	int c, state= 0, ni, ti, vi;

	ni= ti= vi= 0;

	while ((c= fgetc(ifp)) != EOF) {
		if (c == '\n') {
			state= 0;
			if (ni > 0) {
				name[ni++]= 0;
				type[ti++]= 0;
				value[vi++]= 0;
				ni= ti= vi= 0;
				KeyValue(name, value, type);
			}
			Char(c);
			continue;
		}
		switch (state) {
		case 0:             // start of line
			switch (c) {
			case ' ':
			case '\t':
				break;
			case '#':
				state= 1;
				break;
			default:
				state= 2;
				break;
			}
			break;

		case 1:             // comment
			break;

		case 2:             // name
			switch (c) {
			case ' ':
			case '\t':
			case ':':
				state= 3;
				break;
			case '(':
				state= 7;
				break;
			default:
				break;
			}
			break;
		
		case 3:             // ws before value
			if (c != ' ' && c != '\t')
				state= 4;
			break;

		case 4:             // value
			break;

		case 5:             // type
			if (c == ')')
				state= 6;
			break;

		case 6:             // optional ':'
			state= (c == ':') ? 3 : 4;
			break;

		case 7:
			state= (c == ')') ? 6 : 5;
			break;

		}
		switch (state) {
		case 2:
			name[ni++]= c;
			break;
		case 4:
			value[vi++]= c;
			break;
		case 5:
			type[ti++]= c;
			break;
		}
		if (state != 4)
			Char(c);
	}
}

//---- ReadEnvParser -----------------------------------------------------------

class ReadEnvParser: public EnvParser {
	EnvLevel level;
public:
	ReadEnvParser(FILE *f, EnvLevel l) : EnvParser(f)
		{ level= l; }
	void KeyValue(char *name, char *value, char *type)
		{ Env::SetValue(name, value, level, type); }
};

//---- WriteEnvParser ----------------------------------------------------------

class WriteEnvParser: public EnvParser {
	FILE *ofp;
public:
	WriteEnvParser(FILE *f, FILE *of) : EnvParser(f)
		{ ofp= of;  }
	void KeyValue(char *name, char *value, char *type);
	void Char(int c)
		{ fputc(c, ofp); }
};

void WriteEnvParser::KeyValue(char *name, char *value, char*)
{
	EnvRec *er= Env::Lookup(name);
	if (er && er->changed) {
		er->changed= FALSE;
		if (er->op)
			er->Write(er->op);
		fprintf(ofp, "%s", er->value);
	} else
		fprintf(ofp, "%s", value);
}

//---- EnvRec ------------------------------------------------------------------

NewMetaImpl(EnvRec,Object, (T(name), TS(value), TS(type), TE(level), TB(changed)));

EnvRec::EnvRec(const Symbol &n, const char *v, const char *t, EnvLevel l)
{
	name= n;
	value= strsave(v);
	level= l;
	type= strsave(t);
	changed= (l == eEnvChange);
	op= 0;
}

EnvRec::~EnvRec()
{
	SafeDelete(value);
	SafeDelete(type);
}

void EnvRec::Read(Object *op)
{
	IStream is(value, strlen(value));
	op->ReadFrom(is);
}

/*
Object *EnvRec::Read2()
{
	Object *op= 0;
	IStream is(value, strlen(value));
	is >> op;
	return op;
}
*/

void EnvRec::Write(Object *op)
{
	char *cp= new char[1000];
	OStream os(1000, cp);
	op->PrintOn(os);
	ChangeValue(cp, 0, eEnvChange);
	delete cp;
}

/*
void EnvRec::Write2(Object*)
{
}
*/

void EnvRec::ChangeValue(const char *v, const char*, EnvLevel l)
{
	if (l != eEnvChange && level == l) {
		fprintf(stderr,
		"EnvRec::ChangeValue: duplicate entry <%s=%s> for level %d; ignored\n",
							name.AsString(), v, l);
		return;
	}
	if (strcmp(value, v) != 0) {
		if (l == eEnvChange)
			changed= TRUE;
		else {
			changed= FALSE;
			level= l;
		}
		strreplace(&value, v);
		if (changed && op)
			Read(op);
	}
}

void EnvRec::InspectorId(char *buf, int bufSize)
{
	strn0cpy(buf, name.AsString(), bufSize);
}

int EnvRec::Compare(Object *op)
{
	return StrCmp(name.AsString(), Guard(op,EnvRec)->name.AsString(), -1,
						gStdEncoding->SortMap());
}

//---- EnvRec ------------------------------------------------------------------

NewMetaImpl(Env,Object, (TP(table), TB(terminated)));

static Env gEnv;

OrdCollection *Env::table;
bool Env::terminated;

Env::Env()
{
}

Env::~Env()
{
	terminated= TRUE;
	if (table) {
		table->FreeAll();
		SafeDelete(table);
	}
}

void Env::Init()
{
	if (table)
		return;
		
	table= new OrdCollection;
	
	InitSystem();

	ReadFile(form("%s/ETRC", gEtDir), eEnvGlobal);
	ReadFile(form("%s/ETRC", gSystem->HomeDirectory()), eEnvUser);
	ReadFile("ETRC", eEnvLocal);

	const char *e;
	for (int i= 0; e= gSystem->GetEnvAt(i); i++)
		SetValue(e, eEnvEnvironment);
}

EnvRec *Env::Lookup(const char *n)
{
	if (table == 0)
		Env::Init();
	u_int name;
	if (name= (u_int) Symbol::Lookup(n)) {
		Iter next(table);
		EnvRec *er;
		
		while (er= (EnvRec*) next())
			if (er->name.Id() == name)
				return er;
	}
	return 0;
}

//---- Loading/Storing ---------------------------------------------------------

void Env::ReadFile(const char *fname, EnvLevel level)
{
	FILE *ifp;
	if (ifp= fopen(fname, "r")) {
		ReadEnvParser rp(ifp, level);
		rp.Parse();
		fclose(ifp);
	}
}

void Env::Save()
{
	FILE *ifp, *ofp;

	if (ofp= fopen(form("%s/ETRC.new", gSystem->HomeDirectory()), "w")) {
		ifp= fopen(form("%s/ETRC", gSystem->HomeDirectory()), "r");
		if (ifp == 0) {     // try to create file
			ifp= fopen(form("%s/ETRC", gSystem->HomeDirectory()), "w");
			if (ifp)
				fclose(ifp);
		}
		if (ifp= fopen(form("%s/ETRC", gSystem->HomeDirectory()), "r")) {
			WriteEnvParser wp(ifp, ofp);
			wp.Parse();

			Iter next(table);
			EnvRec *er;
			while (er= (EnvRec*) next())
				if (er->changed) {
					er->changed= FALSE;
					fprintf(ofp, "%s %s\n", er->name.AsString(), er->value);
				}
			fclose(ifp);
			fclose(ofp);
			gSystem->Rename(form("%s/ETRC", gSystem->HomeDirectory()),
							form("%s/ETRC.BAK", gSystem->HomeDirectory()));
			gSystem->Rename(form("%s/ETRC.new", gSystem->HomeDirectory()),
							form("%s/ETRC", gSystem->HomeDirectory()));
			return;
		}
		fclose(ofp);
	}
}

//---- GetValue ----------------------------------------------------------------

const char *Env::getvalue(const char *name)
{
	if (terminated)
		return 0;
	EnvRec *er= Lookup(form("%s.%s", gProgname, name));
	if (er == 0)
		er= Lookup(form("*.%s", name));
	if (er == 0)
		er= Lookup(name);
	if (er == 0)
		return 0;
	return er->value;
}

static struct BoolNameTable {
	const char *name;
	int value;
} boolNames[]= {
	{ "TRUE", 1 },
	{ "FALSE", 0 },
	{ "ON", 1 },
	{ "OFF", 0 },
	{ "YES", 1 },
	{ "NO", 0 },
	{ "OK", 1 },
	{ "NOT", 0 },
	{ 0, 0 }
};

int Env::GetValue(const char *name, int dflt)
{
	const char *cp= Env::getvalue(name);
	if (cp) {
		char buf2[100], *cp2= buf2;

		while (Isspace(*cp))
			cp++;
		if (*cp) {
			register BoolNameTable *bt;
			if (Isdigit(*cp))
				return atoi(cp);
			while (Isalpha(*cp))
				*cp2++= gStdEncoding->UpperCaseMap()[*cp++];
			*cp2= 0;
			for (bt= boolNames; bt->name; bt++)
				if (strcmp(buf2, bt->name) == 0)
					return bt->value;
		}
	}
	return dflt;
}

const char *Env::GetValue(const char *name, const char *dflt)
{
	const char *cp= Env::getvalue(name);
	if (cp)
		return cp;
	return dflt;
}

Point Env::GetValue(const char *name, const Point &dflt)
{
	const char *cp= Env::getvalue(name);
	if (cp) {
		Point p;
		IStream is(cp, strlen(cp));
		is >> p;
		return p;
	}
	return dflt;
}

Object *Env::GetValue(const char *name, Object *op)
{
	EnvRec *er= Lookup(form("%s.%s", gProgname, name));
	if (er == 0)
		er= Lookup(form("*.%s", name));
	if (er == 0)
		er= Lookup(name);

	if (er) {
		er->op= op;
		er->Read(op);
	}
	return op;
}

//---- SetValue ----------------------------------------------------------------

void Env::SetValue(const char *name, const char *value, EnvLevel level,
							const char *type)
{
	if (table == 0)
		Env::Init();

	EnvRec *er= Lookup(name);
	if (er)
		er->ChangeValue(value, type, level);
	else
		table->Add(new EnvRec(name, value, type, level));
}

void Env::SetValue(const char *name, EnvLevel level)
{
	char *buf= strsave(name), *cp;
	if (cp= strchr(buf, '=')) {
		*cp++= 0;
		SetValue(buf, cp, level);
	} else
		SetValue(name, "1", level);
	delete buf;
}

void Env::SetValue(const char *name, int value)
{
	SetValue(name, form("%d", value));
}

void Env::SetValue(const char *name, double value)
{
	SetValue(name, form("%f", value));
}

void Env::SetBoolValue(const char *name, bool value)
{
	SetValue(name, value ? "TRUE" : "FALSE");
}

